using System;
using KopiLua;

//see https://github.com/weimingtom/luainterface/blob/master/lua-5.1.4/src/LuaDLL.cpp
namespace Lua511
{
	public class LuaDLL
	{
		//origin luaL_checkmetatable
        public static bool luanet_checkmetatable(Lua.lua_State luaState, int index)
        {
            bool retVal = false;

            if (Lua.lua_getmetatable(luaState, index) != 0)
            {
                Lua.lua_pushlightuserdata(luaState, 0);
                Lua.lua_rawget(luaState, -2);
                retVal = !Lua.lua_isnil(luaState, -1);
                Lua.lua_settop(luaState, -3);
            }
            return retVal;
        }

        public static void luanet_newudata(Lua.lua_State luaState, int val)
        {
            byte[] userdata = Lua.lua_newuserdata(luaState, sizeof(int)) as byte[];
            intToFourBytes(val, userdata);
        }

        public static int luanet_tonetobject(Lua.lua_State luaState, int index)
        {
            byte[] udata;

            if (Lua.lua_type(luaState, index) == Lua.LUA_TUSERDATA)
            {
                if (luanet_checkmetatable(luaState, index))
                {
                    udata = Lua.lua_touserdata(luaState, index) as byte[];
                    if (udata != null)
                    {
                        return fourBytesToInt(udata);
                    }
                }

                udata = checkudata_raw(luaState, index, "luaNet_class") as byte[];
                if (udata != null) return fourBytesToInt(udata);

                udata = checkudata_raw(luaState, index, "luaNet_searchbase") as byte[];
                if (udata != null) return fourBytesToInt(udata);

                udata = checkudata_raw(luaState, index, "luaNet_function") as byte[];
                if (udata != null) return fourBytesToInt(udata);
            }
            return -1;
        }

        public static int luanet_rawnetobj(Lua.lua_State luaState, int obj)
        {
            byte[] bytes = Lua.lua_touserdata(luaState, obj) as byte[];
            return fourBytesToInt(bytes);
        }

        public static int luanet_checkudata(Lua.lua_State luaState, int ud, string tname)
        {
            object udata = checkudata_raw(luaState, ud, tname);

            if (udata != null) return fourBytesToInt(udata as byte[]);
            return -1;
        }

        public static Object luanet_gettag()
        {
            return 0;
        }

        private static int fourBytesToInt(byte[] bytes)
        {
            return bytes[0] + (bytes[1] << 8) + (bytes[2] << 16) + (bytes[3] << 24);
        }

        private static void intToFourBytes(int val, byte[] bytes)
        {
			// gfoot: is this really a good idea?
            bytes[0] = (byte)val;
            bytes[1] = (byte)(val >> 8);
            bytes[2] = (byte)(val >> 16);
            bytes[3] = (byte)(val >> 24);
        }
        
        private static object checkudata_raw(Lua.lua_State L, int ud, string tname)
        {
            object p = Lua.lua_touserdata(L, ud);

            if (p != null)
            {  /* value is a userdata? */
                if (Lua.lua_getmetatable(L, ud) != 0)
                {
                    bool isEqual;

                    /* does it have a metatable? */
                    Lua.lua_getfield(L, Lua.LUA_REGISTRYINDEX, tname);  /* get correct metatable */

                    isEqual = Lua.lua_rawequal(L, -1, -2) != 0;

                    // NASTY - we need our own version of the lua_pop macro
                    // lua_pop(L, 2);  /* remove both metatables */
                    Lua.lua_settop(L, -(2) - 1);

                    if (isEqual)   /* does it have the correct mt? */
                        return p;
                }
            }

            return null;
        }

        //origin lua_tostring
        public static string luanet_tostring(Lua.lua_State luaState, int index)
        {
            // FIXME use the same format string as lua i.e. LUA_NUMBER_FMT
            int t = Lua.lua_type(luaState, index);

            if (t == Lua.LUA_TNUMBER)
                return string.Format("{0}", Lua.lua_tonumber(luaState, index));
            else if (t == Lua.LUA_TSTRING)
            {
                uint strlen;
                return Lua.lua_tolstring(luaState, index, out strlen).ToString();
            }
            else if (t == Lua.LUA_TNIL)
                return null;			// treat lua nulls to as C# nulls
            else
                return "0";	// Because luaV_tostring does this
        }
	}
}
